/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package games;

import java.util.Iterator;

import minmax.GameMove;
import minmax.GameOutcome;
import minmax.GameState;

public class FourInARowState implements GameState<FourInARowState.FourInARowMove> {

    public static final long BIT0 = 1;
    private static final int XSIZE = 7;
    private static final int YSIZE = 6;
    /* The whole board can be stored in a long
     * The board is 6X7 (6 rows 7 columns).
     * We start with first bit (b0) in lower left (0,0)
     * If a bit is 1 it's max player and 0 it's free
     */
    private long maxBoard = 0;
    /* The whole board can be stored in a long
     * The board is 6X7 (6 rows 7 columns).
     * We start with first bit (b0) in lower left (0,0)
     * If a bit is 1 it's min player and 0 it's free
     */
    private long minBoard = 0;
    /* Holds all combinations that wins the game
     */
    private static long[] WINS;
    private boolean player;
    /**
     * cached game result, if any
     */
    private GameOutcome outcome;
    private final FourInARowMove[] moves;
    private int size = 0;

    /*
     * Horizontal wins = XSIZE - 3 on each row: (XSIZE - 3) * YSIZE total 
     * Vertical wins = YSIZE - 3 on each column: (YSIZE - 3) * XSIZE total
     * Diagonal wins = combination of above (YSIZE - 3) * (XSIZE - 3) * 2 total
     */
    static {
        int wins = ((XSIZE - 3) * YSIZE)
                + ((YSIZE - 3) * XSIZE)
                + ((YSIZE - 3) * (XSIZE - 3)) * 2;
        WINS = new long[wins];
        int index = 0;

        //Add horizontal
        for (int y = 0; y < YSIZE; y++) {
            for (int x = 0; x < XSIZE - 3; x++) {
                WINS[index++] = (long)15 << (y * XSIZE + x);
            }
        }

        //Add vertical
        long verticalMask = BIT0 + (BIT0 << XSIZE) + (BIT0 << XSIZE * 2) + (BIT0 << XSIZE * 3);
        for (int x = 0; x < XSIZE; x++) {
            for (int y = 0; y < YSIZE - 3; y++) {
                WINS[index++] = verticalMask << (y * XSIZE + x);
            }
        }

        //Diagonals
        long diagonalMask = BIT0 + (BIT0 << (XSIZE + BIT0)) + (BIT0 << ((XSIZE * 2) + 2)) + (BIT0 << ((XSIZE * 3) + 3));
        long inversediagonalMask = 8 + (BIT0 << (XSIZE + 2)) + (BIT0 << ((XSIZE * 2) + BIT0)) + (BIT0 << ((XSIZE * 3)));
        for (int y = 0; y < YSIZE - 3; y++) {
            for (int x = 0; x < XSIZE - 3; x++) {
                WINS[index++] = diagonalMask << (y * XSIZE + x);
                WINS[index++] = inversediagonalMask << (y * XSIZE + x);
            }
        }
    }

    public FourInARowState() {
        this.moves = new FourInARowMove[XSIZE * YSIZE];
    }

    @Override
    public void move(FourInARowMove step) {
        validateMove(step);

        if (player) {
            this.maxBoard |= step.cellMask;
        } else {
            this.minBoard |= step.cellMask;
        }
        player = !player;
        moves[size] = step;
        size++;

        //TODO: Set outcome
    }

    private void validateMove(FourInARowMove step) throws IllegalArgumentException {
        // validate the move
        if (outcome != null) {
            throw new IllegalStateException("Invalid move: outcome was not null " + step);
        } else if (player != step.player()) {
            throw new IllegalArgumentException("Invalid move: step.player was wrong " + step);
        } else if (!cellIsFree(step)) {
            throw new IllegalArgumentException("Invalid move: cell was not free, mask=" 
                    + step.cellMask + " min=" + minBoard + "max=" + maxBoard +
                    " Collision=" + ((this.maxBoard | this.minBoard) & step.cellMask));
        }
    }

    @Override
    public void backtrack() {
        if (size == 0) {
            throw new IllegalStateException("Not a single move have been performed yet");
        }
        // withdraw the move
        player = !player;
        size--;
        if (player) {
            this.maxBoard &= (~moves[size].cellMask);
        } else {
            this.minBoard &= (~moves[size].cellMask);
        }

        outcome = null;
    }

    @Override
    public Iterator<FourInARowMove> moves() {
        if (outcome != null) {
            throw new IllegalStateException("Game is finished, no further moves are possible");
        }
        return new MoveIterator(player, minBoard, maxBoard);
    }

    @Override
	public boolean canMove(FourInARowMove move) {
		return cell(move.x, move.y) == null;
	}

	@Override
    public boolean player() {
        return player;
    }

    @Override
    public int evaluate(boolean maximize) {
        if (outcome == null) {
            return heuristic();
        } else if (outcome == GameOutcome.Draw) {
            return 0;
        } else {
            boolean player = maximize ? this.player : !this.player;
            boolean winner = outcome.player();
            return winner == player ? Integer.MAX_VALUE - 1 : Integer.MIN_VALUE + 1;
        }
    }

    private int heuristic() {
        return 0; //TODO: implement
    }

    @Override
    public boolean isFinished() {
        return outcome() != null;
    }

    @Override
    public GameOutcome outcome() {
        for (long win : WINS) {
            if ((maxBoard & win) == win) {
                outcome = GameOutcome.Second;
                break;
            }
            if ((minBoard & win) == win) {
                outcome = GameOutcome.First;
                break;
            }
        }
        if (size == XSIZE * YSIZE && outcome == null) {
            outcome = GameOutcome.Draw;
        }
        return outcome;
    }

    public Boolean cell(int x, int y) {
        if ((this.maxBoard & (BIT0 << (y * XSIZE + x))) != 0) {
            return true;
        }
        if ((this.minBoard & (BIT0 << (y * XSIZE + x))) != 0) {
            return false;
        }
        return null;
    }

    @Override
    public GameState<FourInARowMove> clone() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean cellIsFree(FourInARowMove step) {
        return ((this.maxBoard | this.minBoard) & step.cellMask) == 0;
    }

    public static final class FourInARowMove implements GameMove {

        private boolean player;
        private final int bit;
        private final int y;
        private final int x;
        private final long cellMask;

        public FourInARowMove(boolean player, int x, int y) {


            this.player = player;
            this.bit = y * XSIZE + x;
            this.x = x;
            this.y = y;
            this.cellMask = BIT0 << (this.bit);
        }

        @Override
        public boolean player() {
            return player;
        }

        @Override
        public String toString() {
            return "FourInARowMove{" + "player=" + player + ", bit=" + bit + ", y=" + y + ", x=" + x + ", cellMask=" + cellMask + '}';
        }

        public int getBit() {
            return bit;
        }

        public long getCellMask() {
            return cellMask;
        }
        
        
    }

    private static class MoveIterator implements Iterator<FourInARowState.FourInARowMove> {
        public static final long BIT0 = 1;

        private final boolean player;
        private final long minBoard;
        private final long maxBoard;
        private int verticalMask;
        private int col;
        private int x;
        private int y;

        public MoveIterator(boolean player, long minBoard, long maxBoard) {
            this.player = player;
            this.minBoard = minBoard;
            this.maxBoard = maxBoard;

            this.verticalMask = 0;
            for (int i = 0; i < YSIZE; i++) {
                verticalMask += BIT0 << XSIZE * i;
            }

        }

        @Override
        public boolean hasNext() {
            for (; x < XSIZE; x++) {
                for (y = 0; y < YSIZE; y++) {
                    if (((minBoard | maxBoard) & (BIT0 << y * XSIZE + x)) == 0) {
                        //System.out.println("Found cell " + x + "," + y);
                        return true;
                    }
                }
            }

            return false;
        }

        @Override
        public FourInARowMove next() {
            if (!hasNext()) {
                throw new IllegalStateException("Iterator is empty");
            }
            for (; x < XSIZE; x++) {
                for (y = 0; y < YSIZE; y++) {
                    if (((minBoard | maxBoard) & (BIT0 << y * XSIZE + x)) == 0) {
                        return new FourInARowMove(player, x++, y);
                    }
                }
            }


            throw new IllegalStateException("Failed to find move, col:" + this.col);
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }
}
